
De los resultados de búsqueda del search, se pueden obtener datos interesantes sobre cada publicación: un campo valioso es el de sold_quantity que hace referencia al total de ventas históricas de un ítem.
Utilizando los datos de nuestras Apis públicas, el desafío es el de armar un dataset y un modelo que permita predecir con atributos de la publicación el valor de sold_quantity.
Considerando el vistazo dado a los datos en la primera etapa, donde se logró describir y explorar los datos de diversas categorías de productos, surge ahora la necesidad de encontrar patrones y relaciones que se escapan a la vista humana. Para esto, recurrimos a las técnicas de modelado estadístico y/o de Machine Learning que permiten dar con dichos patrones.
Arrancamos entonces con la generación de los datos de manera similar a como se generaron en la etapa previa. Esta vez, nos enfocaremos en la categoría de "Celulares y Smartphones" y la cantidad de unidades vendidas presente en cada publicación de tal manera que se pueda comprender las variables más relacionadas con la estimación de dicha variable, en particular, para dar recomendaciones de negocio que ayuden a aumentar su valor.
import pandas as pd
import numpy as np
import pandas_profiling as pf
import requests
from requests_oauthlib import OAuth1
n = 1000 # registros por categoría
initial_base = pd.DataFrame()
selected_codes = {"MCO1055": "cellphones", "MCO417704":"smartwatches", "MCO14903": "televisions", "MCO4800": "screens", "MCO3690":"audio"}
code = "MCO1055" # El código MCO elegido
base = pd.DataFrame()
for i in range(0, n, 50):
items = requests.get("https://api.mercadolibre.com/sites/MCO/search?category="+ code + "&offset=" + str(i) +
"&limit=50").json() # Para armar las URL
items = pd.json_normalize(items['results']) # Para "aplanar" los JSON
base = base.append(items, ignore_index=True) # Reiniciar índices
base['sub_cat'] = selected_codes[code] # Variable de categoría
initial_base = initial_base.append(base, ignore_index=True)
base.to_csv("data/"+ selected_codes[code] + "_for_model.csv", sep='|', encoding = "utf-8") #Exportar resultados por si no se desea llamar la API cada vez
initial_base.sample(5)
| id | site_id | title | price | sale_price | currency_id | available_quantity | sold_quantity | buying_mode | listing_type_id | ... | seller_address.country.id | seller_address.country.name | seller_address.state.id | seller_address.state.name | seller_address.city.id | seller_address.city.name | seller_address.latitude | seller_address.longitude | sub_cat | differential_pricing.id | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 546 | MCO564938474 | MCO | Celular Nokia 106 | 80990 | None | COP | 1 | 50 | buy_it_now | gold_special | ... | CO | Colombia | CO-DC | Bogotá D.C. | TUNPQ1BVRTg0NTU0 | Puente Aranda | cellphones | NaN | ||
| 946 | MCO586562064 | MCO | Ulefone Armor X5 Pro Rugged Phone 4gb+64gb Ip6... | 830000 | None | COP | 5000 | 1 | buy_it_now | gold_special | ... | CO | Colombia | CO-CUN | Cundinamarca | TUNPQ0NISTk4OWIx | Chía | cellphones | NaN | ||
| 487 | MCO566188079 | MCO | Xiaomi Redmi 8 Ram 4gb 64gb Dual Cámara 5000 Mah | 600000 | None | COP | 1 | 5 | buy_it_now | gold_special | ... | CO | Colombia | CO-RIS | Risaralda | TUNPQ1BFUmMwMjJk | Pereira | cellphones | NaN | ||
| 488 | MCO580269913 | MCO | Celular Huawei P40 Pro 256gb | 3899900 | None | COP | 1 | 1 | buy_it_now | gold_special | ... | CO | Colombia | CO-DC | Bogotá D.C. | TUNPQ0JBUjEyNTcx | Barrios Unidos | cellphones | NaN | ||
| 929 | MCO501266769 | MCO | Celular Libre Honor 8a 32gb Android Pie Lector... | 439900 | None | COP | 1 | 4 | buy_it_now | gold_pro | ... | CO | Colombia | CO-DC | Bogotá D.C. | TUNPQ1VTQTY3MTQ1 | Usaquén | cellphones | 33602181.0 |
5 rows × 56 columns
# Para eliminar duplicados
initial_base = initial_base.groupby(['id']).first().reset_index()
# Repetimos el análisis de variables pero esta vez solo para el subconjunto elegido
profile = pf.ProfileReport(initial_base)
profile
Lo importante del reporte no es lo que muestra sino el uso que se le da a lo presentado. Así, lo que haremos ahora será elegir un subconjunto de variables considerando las advertencias que nos da el anterior reporte a modo de filtros:
Las variables restantes se filtran del dataset y se repite la generación del reporte:
selected_variables = {
"title": "Título de la publicación",
"price": "Precio",
"available_quantity": "Unidades disponibles",
"listing_type_id": "Oro Pro u Oro Especial",
"condition":"Nuevo, Usado o No especificado",
"original_price": "Precio original",
"address.state_name": "Departamento",
"shipping.free_shipping": "Envío gratuito",
# "installments.amount": "Precio de entrega",
# "shipping.store_pick_up": "Recogida en tienda",
"sold_quantity": "Unidades vendidas"
}
staging_base = initial_base.loc[:, selected_variables]
staging_base.shape
(1000, 9)
profile_short = pf.ProfileReport(staging_base)
profile_short
Algunas variables siguen presentando problemas -por ejemplo, "title" presenta una enorme cardinalidad-, pero las dejamos solo porque las ocuparemos para una siguiente etapa de generación de variables. La idea es mejorar la calidad de las variables que entrarán al modelo, y la combinación, adecuación y generación de nuevas variables suelen ser más que útiles para conseguirlo.
Comenzamos:
# title_len: Para obtener la cantidad de palabras en el título
staging_base['title_len'] = staging_base['title'].str.count(" ") + 1
# condition_grouped: Dejar solo nuevo y no nuevo como condiciones del producto
staging_base['condition_grouped'] = np.where(staging_base['condition']=="new", "new", "not_new")
# Variables de descuento: Para saber si hubo y de cuánto fue
staging_base['discount'] = (staging_base['original_price'] / staging_base['price']) - 1
staging_base['discount'] = staging_base['discount'].astype(float)
staging_base['discount_h'] = np.where(pd.notnull(staging_base['discount']), 1,0)
staging_base['discount'] = np.where(pd.notnull(staging_base['discount']), staging_base['discount'],0)
# Más del 60% son publicaciones con Bogotá como ubicación. Puede aportar el separarlas
staging_base['address.state_name_grouped'] = np.where(staging_base['address.state_name']=="Bogotá D.C.", "bogota", "not_bogota")
# Para crear una variable indicadora -1 si el envío es gratuito-.
staging_base['shipping.free_shipping_grouped'] = np.where(staging_base['shipping.free_shipping']==True, 1, 0)
staging_base['shipping.free_shipping_grouped'] = staging_base['shipping.free_shipping_grouped'].astype(str)
# Finalmente, se eliminan las celdas duplicadas
staging_base = staging_base.drop_duplicates()
staging_variables = [
'price',
'available_quantity',
'listing_type_id',
'title_len',
'condition_grouped',
'discount',
# 'discount_h', su correlación con sold_quantity es más baja que la de discount
'address.state_name_grouped',
'shipping.free_shipping_grouped',
'sold_quantity']
staging_base = staging_base.loc[:, staging_variables]
staging_base
| price | available_quantity | listing_type_id | title_len | condition_grouped | discount | address.state_name_grouped | shipping.free_shipping_grouped | sold_quantity | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 49600 | 500 | gold_special | 10 | new | 0.077540 | not_bogota | 0 | 25 |
| 1 | 289900 | 1 | gold_special | 12 | new | 0.000000 | bogota | 1 | 100 |
| 2 | 1799000 | 500 | gold_special | 7 | new | 0.000000 | bogota | 1 | 4 |
| 3 | 385900 | 1 | gold_special | 10 | new | 0.000000 | not_bogota | 1 | 25 |
| 4 | 1499000 | 500 | gold_special | 6 | new | 0.000000 | bogota | 1 | 5 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 995 | 514900 | 1 | gold_special | 7 | new | 0.262187 | bogota | 1 | 2 |
| 996 | 869900 | 1 | gold_special | 13 | new | 0.724221 | not_bogota | 1 | 1 |
| 997 | 1635000 | 1 | gold_special | 14 | new | 0.000000 | bogota | 1 | 1 |
| 998 | 549900 | 1 | gold_special | 11 | new | 0.000000 | not_bogota | 1 | 2 |
| 999 | 599900 | 1 | gold_special | 5 | new | 0.000000 | not_bogota | 1 | 1 |
998 rows × 9 columns
profile_short = pf.ProfileReport(staging_base)
profile_short
En esta parte, revisamos primero si se trata de un problema de aprendizaje supervisado o no supervisado. Como está la variable sold_quantity como guía, hablamos de aprendizaje supervisado. Ahora, nos preguntamos si es un problema de Clasificación o de Regresión. En efecto, es un problema de Regresión siendo la variable numérica sold_quantity nuestra variable de interés y cuya variabilidad intentamos explicar a partir de las demás.
Vale la pena echarle un vistazo a una buena hoja de ruta que sugiere SAS, la popular firma de software estadístico, a la hora de elegir un algoritmo pertinente según la naturaleza de los datos y del problema:

Volviendo a lo nuestro, en lo concerniente a la selección de modelos, iniciamos con modelos simples y vamos aumentando su complejidad según se requiera. Por supuesto, para permitir comparaciones objetivas, tomaremos similares muestras de entrenamiento y prueba aún probando con diferentes modelos.
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler
from sklearn import metrics
from statistics import mean
from regressors import stats
from time import time
Este modelo es bastante potente y solo requiere que todas las variables estén en formato numérico. Como en este caso tenemos variables categóricas, tendremos que convertirlas a dummies primero. El resto es "carpintería":
staging_base.dtypes
price int64 available_quantity int64 listing_type_id object title_len int64 condition_grouped object discount float64 address.state_name_grouped object shipping.free_shipping_grouped object sold_quantity int64 dtype: object
X = staging_base.drop(['sold_quantity'],axis=1) # Nuestra base X con las variables que intentarán explicar Y
y = staging_base['sold_quantity'] # Nuestra variable de interés
X = pd.get_dummies(X, drop_first = True) # drop first es para que mantenga una categoría base en la generación de las dummies
# Dividimos los datos entre train y test (por el tamaño de la base, parece suficiente 75% - 25%, sin validación cruzada)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=2021)
display(X_train.head())
display(y_train.head())
| price | available_quantity | title_len | discount | listing_type_id_gold_special | condition_grouped_not_new | address.state_name_grouped_not_bogota | shipping.free_shipping_grouped_1 | |
|---|---|---|---|---|---|---|---|---|
| 992 | 900000 | 1 | 9 | 0.0 | 0 | 0 | 0 | 1 |
| 620 | 670000 | 1 | 12 | 0.0 | 0 | 0 | 0 | 1 |
| 960 | 645000 | 1 | 9 | 0.0 | 1 | 0 | 0 | 1 |
| 34 | 84990 | 1 | 9 | 0.0 | 0 | 0 | 0 | 1 |
| 670 | 4189900 | 1 | 9 | 0.0 | 1 | 0 | 0 | 1 |
992 5 620 1 960 5 34 2 670 5 Name: sold_quantity, dtype: int64
regressor = LinearRegression()
regressor.fit(X_train, y_train)
LinearRegression()
y_pred = regressor.predict(X_test)
df = pd.DataFrame({'Actual': y_test, 'Predicted': y_pred}) # Para ver las predicciones frente a los datos originales.
df.head(15)
| Actual | Predicted | |
|---|---|---|
| 736 | 4 | 13.341387 |
| 289 | 50 | 51.799925 |
| 705 | 2 | 50.923226 |
| 586 | 1 | 43.790863 |
| 836 | 200 | 242.311663 |
| 282 | 5 | 6.451311 |
| 477 | 25 | 45.467735 |
| 448 | 5 | 46.586263 |
| 191 | 25 | 14.685113 |
| 774 | 2 | 38.318391 |
| 159 | 25 | 40.343819 |
| 907 | 5 | 28.391443 |
| 224 | 5 | 14.152617 |
| 833 | 1 | 37.507895 |
| 570 | 0 | -26.125471 |
print("\n=========== SUMMARY ===========")
xlabels= ['price',
'available_quantity',
'listing_type_id',
'title_len',
'condition_grouped',
'discount',
# 'discount_h', su correlación con sold_quantity es más baja que la de discount
'address.state_name_grouped',
'shipping.free_shipping_grouped']
xlabels=np.asarray(xlabels)
stats.summary(regressor, X_train, y_train, xlabels)
=========== SUMMARY ===========
Residuals:
Min 1Q Median 3Q Max
-49.5052 21.7975 34.4793 50.0197 316.1804
Coefficients:
Estimate Std. Error t value p value
_intercept 2.035905 17.191454 0.1184 0.905762
price -0.000008 0.000002 -3.8931 0.000108
available_quantity 0.007678 0.006596 1.1640 0.244777
listing_type_id 3.789881 0.617690 6.1356 0.000000
title_len 207.444276 18.922167 10.9630 0.000000
condition_grouped 22.180054 8.612833 2.5752 0.010209
discount -33.418911 28.643011 -1.1667 0.243688
address.state_name_grouped -21.343730 6.964163 -3.0648 0.002257
shipping.free_shipping_grouped -8.606443 13.818209 -0.6228 0.533584
---
R-squared: 0.18545, Adjusted R-squared: 0.17663
F-statistic: 21.03 on 8 features
print('Mean Absolute Error:', metrics.mean_absolute_error(y_test, y_pred))
print('Mean Squared Error:', metrics.mean_squared_error(y_test, y_pred))
print('Root Mean Squared Error:', np.sqrt(metrics.mean_squared_error(y_test, y_pred)))
print('R^2:', metrics.r2_score(y_test, y_pred))
Mean Absolute Error: 40.439834840313175 Mean Squared Error: 4230.8907665162915 Root Mean Squared Error: 65.04529780480901 R^2: -0.17151451608263746
Esta nos ayuda a tratar el overfitting e incluso a realizar selección de variables -nos sugiere quitar variables con coeficientes cercanos a cero-. Primero, tenemos que elegir el mejor "alpha" para utilizar su método de regularización:
# Para elegir el mejor alpha
train_score = []
test_score = []
alpha_vals = []
for k in np.arange(0.0, 1, 0.001).tolist():
alpha_vals.append(k)
model = Lasso(alpha = k)
model.fit(X_train, y_train)
y_predt = model.predict(X_train)
y_pred = model.predict(X_test)
tr_score = metrics.mean_squared_error(y_train, y_predt)
train_score.append(tr_score)
te_score = metrics.mean_squared_error(y_test, y_pred)
test_score.append(te_score)
<ipython-input-26-f0c2fb96fcae>:9: UserWarning: With alpha=0, this algorithm does not converge well. You are advised to use the LinearRegression estimator model.fit(X_train, y_train) C:\Users\juand\anaconda3\lib\site-packages\sklearn\linear_model\_coordinate_descent.py:529: UserWarning: Coordinate descent with no regularization may lead to unexpected results and is discouraged. model = cd_fast.enet_coordinate_descent( C:\Users\juand\anaconda3\lib\site-packages\sklearn\linear_model\_coordinate_descent.py:529: ConvergenceWarning: Objective did not converge. You might want to increase the number of iterations. Duality gap: 2933366.1211921712, tolerance: 720.2422998663103 model = cd_fast.enet_coordinate_descent(
# Para ver cómo cambian los errores con diferentes valores de alpha
resultados = pd.DataFrame([alpha_vals, train_score, test_score])
resultados = resultados.transpose()
resultados.columns = ['alpha', "MSE Train", "MSE Test"]
resultados
| alpha | MSE Train | MSE Test | |
|---|---|---|---|
| 0 | 0.000 | 7843.224923 | 4230.890767 |
| 1 | 0.001 | 7843.225088 | 4230.507453 |
| 2 | 0.002 | 7843.225582 | 4230.124571 |
| 3 | 0.003 | 7843.226405 | 4229.742122 |
| 4 | 0.004 | 7843.227557 | 4229.360106 |
| ... | ... | ... | ... |
| 995 | 0.995 | 7899.173996 | 3948.385059 |
| 996 | 0.996 | 7899.255217 | 3948.139225 |
| 997 | 0.997 | 7899.336520 | 3947.893472 |
| 998 | 0.998 | 7899.417905 | 3947.647797 |
| 999 | 0.999 | 7899.499371 | 3947.402202 |
1000 rows × 3 columns
lassoModel = Lasso(alpha = 0.004)
lassoModel.fit(X_train, y_train)
print(lassoModel.score(X_test, y_test))
-0.17109068307405484
# Para ver los coeficientes
coeff_df = pd.DataFrame(zip(X.columns,lassoModel.coef_))
coeff_df.sort_values(1, ascending = False)
| 0 | 1 | |
|---|---|---|
| 3 | discount | 207.315607 |
| 4 | listing_type_id_gold_special | 22.141176 |
| 2 | title_len | 3.787286 |
| 1 | available_quantity | 0.007684 |
| 0 | price | -0.000008 |
| 7 | shipping.free_shipping_grouped_1 | -8.474103 |
| 6 | address.state_name_grouped_not_bogota | -21.333159 |
| 5 | condition_grouped_not_new | -33.073402 |
y_pred = lassoModel.predict(X_test)
print('Mean Absolute Error:', metrics.mean_absolute_error(y_test, y_pred))
print('Mean Squared Error:', metrics.mean_squared_error(y_test, y_pred))
print('Root Mean Squared Error:', np.sqrt(metrics.mean_squared_error(y_test, y_pred)))
Mean Absolute Error: 40.43483079585518 Mean Squared Error: 4229.360105873217 Root Mean Squared Error: 65.03353062746338
Podría probarse también con la regularización L2, para penalizar los coeficientes grandes a favor de los pequeños, exactamente lo que posibilita la Regresión Ridge. De nuevo, lo primero es elegir un buen alpha para trabajar:
# Para elegir el mejor alpha
train_score = []
test_score = []
alpha_vals = []
for k in np.arange(0, 3, 0.001).tolist():
alpha_vals.append(k)
model = Ridge(alpha = k)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
y_predt = model.predict(X_train)
tr_score = metrics.mean_squared_error(y_train, y_predt)
train_score.append(tr_score)
te_score = metrics.mean_squared_error(y_test, y_pred)
test_score.append(te_score)
resultados = pd.DataFrame([alpha_vals, train_score, test_score])
resultados = resultados.transpose()
resultados.columns = ['alpha', "MSE Train", "MSE Test"]
resultados
| alpha | MSE Train | MSE Test | |
|---|---|---|---|
| 0 | 0.000 | 7000.081154 | 5602.725532 |
| 1 | 0.001 | 7000.081156 | 5602.746627 |
| 2 | 0.002 | 7000.081161 | 5602.767723 |
| 3 | 0.003 | 7000.081169 | 5602.788819 |
| 4 | 0.004 | 7000.081181 | 5602.809916 |
| ... | ... | ... | ... |
| 2995 | 2.995 | 7011.696141 | 5667.322222 |
| 2996 | 2.996 | 7011.703002 | 5667.343883 |
| 2997 | 2.997 | 7011.709865 | 5667.365543 |
| 2998 | 2.998 | 7011.716730 | 5667.387203 |
| 2999 | 2.999 | 7011.723595 | 5667.408863 |
3000 rows × 3 columns
ridgeModel = Ridge(alpha=0.001, max_iter=10e5)
ridgeModel.fit(X_train,y_train)
Ridge(alpha=0.001, max_iter=1000000.0)
print("\n=========== SUMMARY ===========")
xlabels=np.asarray(xlabels)
stats.summary(ridgeModel, X_train, y_train, xlabels)
=========== SUMMARY ===========
Residuals:
Min 1Q Median 3Q Max
-85.271 21.712 32.6552 47.3941 276.5458
Coefficients:
Estimate Std. Error t value p value
_intercept -11.186571 16.889370 -0.6623 0.507955
price -0.000006 0.000002 -3.5716 0.000377
available_quantity 0.007135 0.006225 1.1463 0.252045
listing_type_id 3.845845 0.573344 6.7077 0.000000
title_len 168.796187 17.405929 9.6976 0.000000
condition_grouped 21.846230 8.144909 2.6822 0.007476
discount -17.917102 27.306480 -0.6561 0.511931
address.state_name_grouped -18.037608 6.550973 -2.7534 0.006041
shipping.free_shipping_grouped 1.271214 13.150434 0.0967 0.923017
---
R-squared: 0.15387, Adjusted R-squared: 0.14471
F-statistic: 16.80 on 8 features
y_pred = ridgeModel.predict(X_test)
print('Mean Absolute Error:', metrics.mean_absolute_error(y_test, y_pred))
print('Mean Squared Error:', metrics.mean_squared_error(y_test, y_pred))
print('Root Mean Squared Error:', np.sqrt(metrics.mean_squared_error(y_test, y_pred)))
Mean Absolute Error: 42.75344499602701 Mean Squared Error: 5602.746626926753 Root Mean Squared Error: 74.85149715888623
Lo de elástica viene porque intenta equilibrar ambos tipos de regularización, el de la regresión Lasso y el de la regresión Ridge, pero esta vez la saltamos pues las primeras no dieron muy buenos resultados.
El último modelo por revisar es la generalización de los bosques aleatorios, Random Forest, útil en problemas de Clasificación así como en problemas de Regresión. Considerando la cantidad de variables categóricas, probablemente dará mejores resultados que los anteriores modelos. Arrancamos con una ligera trampa y es escalar las variables antes de meterlas al modelo.
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
Lanzamos enseguida un modelo Random Forest con un conjunto de parámetros básicos:
predictors_importance = pd.DataFrame(
{'predictor': X.columns,
'importance': rfModel.feature_importances_}
)
predictors_importance.sort_values('importance', ascending=False).head(10)
| predictor | importance | |
|---|---|---|
| 0 | price | 0.406911 |
| 3 | discount | 0.299518 |
| 2 | title_len | 0.170049 |
| 1 | available_quantity | 0.079937 |
| 6 | address.state_name_grouped_not_bogota | 0.032031 |
| 4 | listing_type_id_gold_special | 0.008760 |
| 7 | shipping.free_shipping_grouped_1 | 0.002530 |
| 5 | condition_grouped_not_new | 0.000264 |
rfModel = RandomForestRegressor(n_estimators = 100, max_depth= 20, max_features= 8, random_state = 2021)
rfModel.fit(X_train, y_train)
print('Error en entrenamiento: {}'.format(1-rfModel.score(X_train, y_train)))
print('Error en prueba: {}'.format(1-rfModel.score(X_test, y_test)))
Error en entrenamiento: 0.17094422607949522 Error en prueba: 0.8062427108392003
print('Score en entrenamiento: {}'.format(rfModel.score(X_train, y_train)))
print('Score en prueba: {}'.format(rfModel.score(X_test, y_test)))
Score en entrenamiento: 0.8290557739205048 Score en prueba: 0.1937572891607997
Entonces, en vez de hacerlo con parámetros "sacados del bolsillo", podemos armar una grilla de parámetros para probar combinaciones (en este caso, de número de árboles y porcentaje de características) y usar validación cruzada para tratar con el tema del sobreajuste.
# Grilla de parámetros
max_features_params = [np.round(10**-1 * i, decimals=1) for i in range(1, 11, 1)]
param_grid = {'n_estimators': [2**i for i in range(2, 12, 1)], 'max_features': max_features_params}
print('Número de árboles: {}'.format(param_grid['n_estimators']))
print('Porcentaje de características a usar: {}'.format(param_grid['max_features']))
Número de árboles: [4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048] Porcentaje de características a usar: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.8, random_state=2021)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
start = time()
reg = GridSearchCV(RandomForestRegressor(), param_grid=param_grid, verbose=1, n_jobs=-1, cv=2)
reg.fit(X_train, y_train)
print("GridSearchCV tomó {} segundos usando {} configuraciones".format(time() - start,
len(clf.cv_results_['params'])))
Fitting 2 folds for each of 100 candidates, totalling 200 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 4 concurrent workers. [Parallel(n_jobs=-1)]: Done 57 tasks | elapsed: 21.9s
GridSearchCV tomó 91.81499910354614 segundos usando 100 configuraciones
[Parallel(n_jobs=-1)]: Done 200 out of 200 | elapsed: 1.5min finished
print(reg.best_params_)
print(reg.best_score_)
{'max_features': 0.3, 'n_estimators': 16}
0.09906628157448572
print(reg.score(X_train, y_train))
print(reg.score(X_test, y_test))
0.8250428102779653 0.1767690369821534
Claramente hace falta más trabajo en la parte de la generación de los datos para dar con una muestra más grande, generar nuevas variables y probar más configuraciones de modelos. Con datos más arreglados, y con muchas más muestras, podría probarse un XGBoost o una red neuronal, pero por ahora, no recurrimos a ellos hasta no mejorar la calidad de la data inicial.
En cuanto a recomendaciones, sí alcanza a haber pista de que los descuentos sobresalen en cuanto a la influencia en la cantidad de unidades vendidas, así que no sobra animar a la generación de campañas a favor de la publicación de descuentos por parte de los vendedores colombianos.